package com.gitee.wsl.struct.version

//package com.helger.commons.version;

import com.gitee.wsl.ext.base.ifValue
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmOverloads

/**
 * This class represents a single version object. It supports 4 elements: major
 * version (integer), minor version (integer), micro version (integer) and a
 * qualifier (string).
 *
 * @author Philip Helger
 */
@Serializable(with = VersionSerializer::class)
class Version @JvmOverloads constructor(
    val major: Int=0,
    val minor: Int=0,
    val micro: Int=0,
    val qualifier: String = ""
) : Comparable<Version> {

    fun hasQualifier(): Boolean {
        return qualifier.isNotEmpty()
    }

    /**
     * Determines whether the version is considered stable or not.
     * Stable versions have a positive major number and no pre-release identifier.
     */
    val isStable: Boolean = major > 0 && qualifier.isEmpty()

    /**
     * Compares two Version objects.
     *
     * @param other
     * the version to compare to
     * @return &lt; 0 if this is less than rhs; &gt; 0 if this is greater than
     * rhs, and 0 if they are equal.
     * @throws IllegalArgumentException
     * if the parameter is null
     */
    override operator fun compareTo(other: Version): Int {
        // compare major version
        var ret = major - other.major
        if (ret == 0) {
            // compare minor version
            ret = minor - other.minor
            if (ret == 0) {
                // compare micro version
                ret = micro - other.micro
                if (ret == 0) {
                    // check qualifier
                    if (qualifier.isNotEmpty()) {
                        if (other.qualifier.isNotEmpty()) {
                            ret = qualifier.compareTo(other.qualifier)
                            // convert to -1/0/+1
                            if (ret < 0) ret = -1 else if (ret > 0) ret = 1
                        } else ret = 1
                    } else if (other.qualifier.isNotEmpty()) {
                        // only this qualifier == null
                        ret = -1
                    } else {
                        // both qualifier are null
                        ret = 0
                    }
                }
            }
        }
        return ret
    }

    val asString: String
        get() = getAsString(DEFAULT_PRINT_ZERO_ELEMENTS)

    /**
     * Get the string representation of the version number.
     *
     * @param bPrintZeroElements
     * If `true` than trailing zeroes are printed, otherwise
     * printed zeroes are not printed.
     * @return Never `null`.
     */

    fun getAsString(bPrintZeroElements: Boolean): String {
        return getAsString(bPrintZeroElements, false)
    }

    /**
     * Get the string representation of the version number.
     *
     * @param bPrintZeroElements
     * If `true` than trailing zeroes are printed, otherwise
     * printed zeroes are not printed.
     * @param bPrintAtLeastMajorAndMinor
     * `true` if major and minor part should always be printed,
     * independent of their value
     * @return Never `null`.
     */
    fun getAsString(bPrintZeroElements: Boolean, bPrintAtLeastMajorAndMinor: Boolean): String {
        // Build from back to front
        val aSB = StringBuilder(qualifier?:"")
        if (micro > 0 || aSB.isNotEmpty() || bPrintZeroElements) {
            // Micro version
            if (aSB.isNotEmpty()) aSB.insert(0, '.')
            aSB.insert(0, micro)
        }
        if (bPrintAtLeastMajorAndMinor || minor > 0 || aSB.isNotEmpty() || bPrintZeroElements) {
            // Minor version
            if (aSB.isNotEmpty()) aSB.insert(0, '.')
            aSB.insert(0, minor)
        }
        if (bPrintAtLeastMajorAndMinor || major > 0 || aSB.isNotEmpty() || bPrintZeroElements) {
            // Major version
            if (aSB.isNotEmpty()) aSB.insert(0, '.')
            aSB.insert(0, major)
        }
        return if (aSB.isNotEmpty()) aSB.toString() else DEFAULT_VERSION_STRING
    }

    /**
     * Get the string representation of the version number but only major and
     * minor version number.
     *
     * @return Never `null`.
     */
    val asStringMajorMinor: String
        get() = "$major.$minor"

    /**
     * Get the string representation of the version number but only major and
     * minor and micro version number.
     *
     * @return Never `null`.
     */

    val asStringMajorMinorMicro: String
        get() = "$major.$minor.$micro"

    override fun equals(other: Any?): Boolean {
        if (other === this) return true
        if (other == null || this::class != other::class) return false
        val rhs = other as Version
        return major == rhs.major && minor == rhs.minor && micro == rhs.micro && qualifier == rhs.qualifier
    }

    override fun hashCode(): Int {
        var result = major
        result = 31 * result + minor
        result = 31 * result + micro
        result = 31 * result + (qualifier.hashCode() ?: 0)
        return result
    }

    /**
     * Constructs a copy of the [Version]. The copied object's properties can be altered with the optional parameters.
     *
     * @sample io.github.z4kn4fein.semver.samples.VersionSamples.copy
     */
     fun copy(
        major: Int = this.major,
        minor: Int = this.minor,
        patch: Int = this.micro,
        preRelease: String = this.qualifier,
    ): Version = Version(major, minor, patch, preRelease)


    /** Component function that returns the MAJOR number of the version upon destructuring. */
     operator fun component1(): Int = major

    /** Component function that returns the MINOR number of the version upon destructuring. */
     operator fun component2(): Int = minor

    /** Component function that returns the PATCH number of the version upon destructuring. */
     operator fun component3(): Int = micro

    /** Component function that returns the PRE-RELEASE identifier of the version upon destructuring. */
     operator fun component4(): String = qualifier


    companion object {
        /** default version if nothing is specified.  */
        const val DEFAULT_VERSION_STRING = "0"

        val DEFAULT_VERSION = Version(0, 0, 0)

        /** Default value for printing zero elements in getAsString  */
        const val DEFAULT_PRINT_ZERO_ELEMENTS = false

        private val versionRegex: Regex = Patterns.VERSION_REGEX.toRegex()
        private val looseVersionRegex: Regex = Patterns.LOOSE_VERSION_REGEX.toRegex()


        private fun _extSplit( s: String): List<String> {
            val aDotParts =  s.splitToSequence('.').toList()
            if (aDotParts.size == 2) {
                // Dots always take precedence
                return aDotParts
            }
            if (aDotParts[0].toIntOrNull() == null) {
                // If it is numeric, use the dot parts anyway (e.g. for "5" or "-1")
                return aDotParts
            }
            val aDashParts = s.splitToSequence('-').toList()
            return (aDashParts.size == 1).ifValue(aDotParts,aDashParts)
            // More matches for dash split! (e.g. "0-RC1")
        }

        /**
         * Construct a version object from a string.<br></br>
         * EBNF:<br></br>
         * version ::= major( '.' minor ( '.' micro ( ( '.' | '-' ) qualifier )? )? )?
         * <br></br>
         * major ::= number<br></br>
         * minor ::= number<br></br>
         * micro ::= number<br></br>
         * qualifier ::= .+
         *
         * @param sVersionString
         * the version string to be interpreted as a version
         * @return The parsed [Version] object.
         * @throws IllegalArgumentException
         * if any of the parameters is &lt; 0
         */

        fun parse(sVersionString: String): Version {
            val s = sVersionString.trim { it <= ' ' }
            if (s.isEmpty()) return DEFAULT_VERSION

            // Complex parsing
            var aMajor: Int? = null
            var aMinor: Int? = null
            var aMicro: Int? = null
            var sQualifier: String?
            var bDone = false

            // Extract major version number
            var aParts = _extSplit(s)
            aMajor = aParts[0].toIntOrNull()
            if (aParts[0].isNotEmpty()) {
                // Major version is not numeric, so everything is the qualifier
                sQualifier = s
                bDone = true
            } else sQualifier = null
            var sRest = if (!bDone && aParts.size > 1) aParts[1] else null
            if (!sRest.isNullOrEmpty()) {
                // Parse minor version number part
                aParts = _extSplit(sRest)
                aMinor = aParts[0].toIntOrNull()
                if (aMinor == null && aParts[0].isNotEmpty()) {
                    // Minor version is not numeric, so everything is the qualifier
                    sQualifier = sRest
                    bDone = true
                }
                sRest = if (!bDone && aParts.size > 1) aParts[1] else null
                if (!sRest.isNullOrEmpty()) {
                    // Parse micro version number part
                    aParts = _extSplit(sRest)
                    aMicro = aParts[0].toIntOrNull()
                    if (aMicro == null && aParts[0].isNotEmpty()) {
                        // Micro version is not numeric, so everything is the qualifier
                        sQualifier = sRest
                        bDone = true
                    }
                    if (!bDone && aParts.size > 1) {
                        // Some qualifier left!
                        sQualifier = aParts[1]
                    }
                }
            }
            val nMajor = aMajor ?: 0
            val nMinor = aMinor ?: 0
            val nMicro = aMicro ?: 0
            sQualifier = if (sQualifier.isNullOrEmpty()) "" else sQualifier
            return Version(nMajor, nMinor, nMicro, sQualifier)
        }

        /**
         * Parses the [versionString] as a [Version] and returns the result or throws a [VersionFormatException]
         * if the string is not a valid representation of a semantic version.
         *
         * Strict mode is on by default, which means partial versions (e.g. '1.0' or '1') and versions with 'v' prefix
         * are considered invalid. This behaviour can be turned off by setting [strict] to false.
         *
         * @sample io.github.z4kn4fein.semver.samples.VersionSamples.parseStrict
         * @sample io.github.z4kn4fein.semver.samples.VersionSamples.parseLoose
         */
        @Suppress("MagicNumber")
        fun parse(
            versionString: String,
            strict: Boolean,
        ): Version {
            val regex = if (strict) versionRegex else looseVersionRegex
            val result = regex.matchEntire(versionString) ?: throw VersionFormatException("Invalid version: $versionString")
            val major = result.groupValues[1].toIntOrNull()
            val minor = result.groupValues[2].toIntOrNull()
            val patch = result.groupValues[3].toIntOrNull()
            val preRelease = result.groups[4]?.value?:""
            val buildMetadata = result.groups[5]?.value

            return when {
                strict && major != null && minor != null && patch != null ->
                    Version(major, minor, patch, preRelease)
                !strict && major != null ->
                    Version(major, minor ?: 0, patch ?: 0, preRelease)
                else -> throw VersionFormatException("Invalid version: $versionString")
            }
        }


        /**
         * Construct a version object from a string.<br></br>
         * EBNF:<br></br>
         * version ::= major( '.' minor ( '.' micro ( '.' qualifier )? )? )? <br></br>
         * major ::= number<br></br>
         * minor ::= number<br></br>
         * micro ::= number<br></br>
         * qualifier ::= .+
         *
         * @param sVersionString
         * the version string to be interpreted as a version
         * @return The parsed [Version] object.
         * @throws IllegalArgumentException
         * if any of the parameters is &lt; 0
         * @since v10.0.1
         */
        fun parseDotOnly(sVersionString: String): Version {
            val s = sVersionString.trim { it <= ' ' }
            if (s.isEmpty()) return DEFAULT_VERSION
            val nMajor: Int
            val nMinor: Int
            val nMicro: Int
            val sQualifier: String?

            // split each token
            val aParts =  s.splitToSequence('.').toList()
            nMajor = if (aParts.isNotEmpty()) aParts[0].toIntOrNull()?:0 else 0
            nMinor = if (aParts.size > 1) aParts[1].toIntOrNull()?:0 else 0
            nMicro = if (aParts.size > 2) aParts[2].toIntOrNull()?:0 else 0
            sQualifier = if (aParts.size > 3) aParts[3].ifEmpty { "" } else ""
            return Version(nMajor, nMinor, nMicro, sQualifier)
        }
    }
}

/**
 * Parses the string as a [Version] and returns the result or throws a [VersionFormatException]
 * if the string is not a valid representation of a semantic version.
 *
 * Strict mode is on by default, which means partial versions (e.g. '1.0' or '1') and versions with 'v' prefix are
 * considered invalid. This behaviour can be turned off by setting [strict] to false.
 *
 * @sample io.github.z4kn4fein.semver.samples.VersionSamples.toVersionStrict
 * @sample io.github.z4kn4fein.semver.samples.VersionSamples.toVersionLoose
 */
 fun String.toVersion(strict: Boolean = true): Version = Version.parse(this, strict)

/**
 * Parses the string as a [Version] and returns the result or null
 * if the string is not a valid representation of a semantic version.
 *
 * Strict mode is on by default, which means partial versions (e.g. '1.0' or '1') and versions with 'v' prefix are
 * considered invalid. This behaviour can be turned off by setting [strict] to false.
 *
 * @sample io.github.z4kn4fein.semver.samples.VersionSamples.toVersionOrNullStrict
 * @sample io.github.z4kn4fein.semver.samples.VersionSamples.toVersionOrNullLoose
 */
 fun String.toVersionOrNull(strict: Boolean = true): Version? =
    try {
        this.toVersion(strict)
    } catch (_: Exception) {
        null
    }

internal fun String.toPreRelease(): PreRelease = PreRelease(this)
